#pragma once

#ifndef _TreeBase_h_
#define _TreeBase_h_

#include "../d3d/d3dUtility.h"

#include <vector>

namespace Framework {

using namespace std;

template<typename T>
class TreeBase abstract {
public:
    virtual ~TreeBase(void);
    d3d::BoundingBox* GetBoundingBox() const;
    d3d::BoundingSphere* GetBoundingSphere() const;
    vector<TreeBase<T>*>* GetChildren() const;
    T GetItem() const;
    void SetItem(T val);
    const TreeBase<T>* FindChild(D3DXVECTOR3 position) const;
    vector<TreeBase<T>*>* FindVisible(Camera* camera);
protected:
    TreeBase(d3d::BoundingBox* boundingBox, FLOAT treshold, const TreeBase<T>* parent);
    d3d::BoundingBox* boundingBox;
    d3d::BoundingSphere* boundingSphere;
    vector<TreeBase<T>*>* children;
    T item;
    const TreeBase<T>* parent;
    FLOAT treshold;
    virtual TreeBase<T>* Create(FLOAT minX, FLOAT minY, FLOAT minZ, FLOAT maxX, FLOAT maxY, FLOAT maxZ) const = 0;
    void FindVisible(Camera* camera, TreeBase<T>* root, vector<TreeBase<T>*>** visible);
};


template<typename T>
TreeBase<T>::TreeBase(d3d::BoundingBox* boundingBox, FLOAT treshold, const TreeBase<T>* parent) :
        boundingBox(boundingBox), treshold(treshold), parent(parent) {
}

template<typename T>
TreeBase<T>::~TreeBase(void) {
}

template<typename T>
d3d::BoundingBox* TreeBase<T>::GetBoundingBox() const {
    return this->boundingBox;
}

template<typename T>
d3d::BoundingSphere* TreeBase<T>::GetBoundingSphere() const {
    return this->boundingSphere;
}

template<typename T>
vector<TreeBase<T>*>* TreeBase<T>::GetChildren() const {
    return this->children;
}

template<typename T>
T TreeBase<T>::GetItem() const {
    return this->item;
}

template<typename T>
void TreeBase<T>::SetItem(T val) {
    this->item = val;
}

template<typename T>
const TreeBase<T>* TreeBase<T>::FindChild(D3DXVECTOR3 position) const {
    if (!this->boundingBox->isPointInside(position)) {
        return NULL;
    }

    for (vector<TreeBase*>::iterator it = this->children->begin(); it != this->children->end(); ++it) {
        const TreeBase<T>* child = (*it)->FindChild(position);
        if (child != NULL) {
            return child->FindChild(position);
        }
    }

    return this;
}

template<typename T>
vector<TreeBase<T>*>* TreeBase<T>::FindVisible(Camera* camera) {
    vector<TreeBase*>* visible = new vector<TreeBase*>();
    this->FindVisible(camera, this, &visible);
    return visible;
}

template<typename T>
void TreeBase<T>::FindVisible(Camera* camera, TreeBase<T>* root, vector<TreeBase<T>*>** visible) {
    FrustumCullingType sphereCulling = camera->SphereInFrustum(root->GetBoundingSphere());
    if (sphereCulling == FRUSTUMCULLINGTYPE_NONE) {
        return;
    } else if (sphereCulling == FRUSTUMCULLINGTYPE_FULL) {
        FrustumCullingType cameraViewDistanceCulling = camera->SphereInVisibleRadius(root->GetBoundingSphere());
        if (cameraViewDistanceCulling == FRUSTUMCULLINGTYPE_FULL || (cameraViewDistanceCulling == FRUSTUMCULLINGTYPE_PARTIAL && root->GetChildren()->size() == 0)) {
            (*visible)->push_back(root);
        } else if (cameraViewDistanceCulling == FRUSTUMCULLINGTYPE_PARTIAL) {
            for (vector<TreeBase<T>*>::iterator it = root->GetChildren()->begin(); it != root->GetChildren()->end(); ++it) {
                this->FindVisible(camera, *it, visible);
            }
        } else {
            return;
        }
    } else {
        FrustumCullingType culling = camera->BoxInFrustum(root->GetBoundingBox());
        if (culling == FRUSTUMCULLINGTYPE_FULL || (culling == FRUSTUMCULLINGTYPE_PARTIAL && root->GetChildren()->size() == 0)) {
            FrustumCullingType cameraViewDistanceCulling = camera->SphereInVisibleRadius(root->GetBoundingSphere());
            if (cameraViewDistanceCulling == FRUSTUMCULLINGTYPE_FULL || (cameraViewDistanceCulling == FRUSTUMCULLINGTYPE_PARTIAL && root->GetChildren()->size() == 0)) {
                (*visible)->push_back(root);
            } else if (cameraViewDistanceCulling == FRUSTUMCULLINGTYPE_PARTIAL) {
                for (vector<TreeBase<T>*>::iterator it = root->GetChildren()->begin(); it != root->GetChildren()->end(); ++it) {
                    this->FindVisible(camera, *it, visible);
                }
            } else {
                return;
            }
        } else if (culling == FRUSTUMCULLINGTYPE_PARTIAL) {
            for (vector<TreeBase<T>*>::iterator it = root->GetChildren()->begin(); it != root->GetChildren()->end(); ++it) {
                this->FindVisible(camera, *it, visible);
            }
        }
    }
}

}

#endif
